namespace Yaaf.Wp7.AdvancedExplorer.Uploading
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.ServiceModel;
    using System.Threading;

    using Yaaf.Wp7.AdvancedExplorer.FileExchangeService;

    public interface ITask
    {
        void Start();
        bool IsStarted { get; }
        bool IsFinished { get; }
        event EventHandler<TaskFinishedEventArgs> Finished;
        event EventHandler<TaskUpdateEventArgs> Update;
        event EventHandler<TaskErrorEventArgs> Error;
    }

    public class TaskErrorEventArgs : EventArgs
    {
        public TaskErrorEventArgs(Exception error)
        {
            this.Error = error;
        }

        public Exception Error { get; private set; }
    }

    public abstract class Task : ITask
    {
        private bool isStarted;

        private bool isFinished;

        public void Start()
        {
            if (IsStarted)
            {
                return;
            }
            isStarted = true;

            ThreadPool.QueueUserWorkItem(DoWork);
        }

        private void DoWork(object state)
        {
            try
            {
                this.DoTask();
            }
            catch (Exception e)
            {
                this.InvokeError(new TaskErrorEventArgs(e));
            }
        }

        public abstract void DoTask();

        public bool IsStarted
        {
            get
            {
                return isStarted;
            }
        }

        public bool IsFinished
        {
            get
            {
                return isFinished;
            }
        }

        public event EventHandler<TaskFinishedEventArgs> Finished;

        protected void InvokeFinished(TaskFinishedEventArgs e)
        {
            isFinished = true;
            EventHandler<TaskFinishedEventArgs> handler = this.Finished;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        public event EventHandler<TaskErrorEventArgs> Error;

        protected void InvokeError(TaskErrorEventArgs e)
        {
            EventHandler<TaskErrorEventArgs> handler = this.Error;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        public event EventHandler<TaskUpdateEventArgs> Update;

        protected void InvokeUpdate(TaskUpdateEventArgs e)
        {
            EventHandler<TaskUpdateEventArgs> handler = this.Update;
            if (handler != null)
            {
                handler(this, e);
            }
        }
    }

    public class DefaultTask : Task
    {
        private readonly Action workItem;

        public DefaultTask(Action workItem)
        {
            this.workItem = workItem;
        }

        public override void DoTask()
        {
            workItem();
        }
    }

    public class UploadTask : ITask
    {
        private readonly Func<Stream> getStream;

        private readonly string path;

        private FileExchangeServiceClient client;

        private Guid handle;

        private byte[] buffer = new byte[ServiceConnection.SendBufferSize];

        private bool isStarted;

        private bool isFinished;

        private Stream stream;

        private Timer timer;

        public UploadTask(Func<Stream> getStream, string path)
        {
            this.getStream = getStream;
            this.path = path;
        }

        public void Start()
        {
            if (isStarted)
            {
                return;
            }
            isStarted = true;

            stream = getStream();
            client = ServiceConnection.GetClient();

            timer = new Timer(TimeoutReached, null, ServiceConnection.TimeOut, ServiceConnection.TimeOut);
            client.CloseCompleted += new EventHandler<AsyncCompletedEventArgs>(client_CloseCompleted);
            client.StartCompleted += new EventHandler<StartCompletedEventArgs>(client_StartCompleted);
            client.StartAsync(this.path, false);

        }

        void TimeoutReached(object state)
        {
            if (timer != null)
            {
                timer.Dispose();
                timer = null;
            }

            if (!isFinished)
            {
                if (client != null)
                {
                    var thread = new Thread(
                        () => this.client.Abort());
                    thread.Start();
                    if (!thread.Join(1000))
                    {
                        thread.Abort();
                    }
                    client = null;
                }

                this.InvokeError(new TaskErrorEventArgs(new TimeoutException("Timeout reached!")));
            }
        }

        void client_CloseCompleted(object sender, AsyncCompletedEventArgs e)
        {
            if (ErrorOccured(e))
            {
                return;
            }

            if (!isFinished)
            {
                this.InvokeFinished(new TaskFinishedEventArgs(null));
            }
        }

        void RefreshTimer()
        {
            if (timer != null)
            {
                timer.Change(ServiceConnection.TimeOut, ServiceConnection.TimeOut);
            }
        }

        void client_StartCompleted(object sender, StartCompletedEventArgs e)
        {
            RefreshTimer();
            if (ErrorOccured(e))
            {
                return;
            }
            handle = e.Result;
            client.SendDataCompleted += new EventHandler<AsyncCompletedEventArgs>(client_SendDataCompleted);
            SendFileJunk();
        }

        private bool ErrorOccured(AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                if (timer != null)
                {
                    timer.Dispose();
                    timer = null;
                }
                this.InvokeError(new TaskErrorEventArgs(e.Error));
                return true;
            }

            return false;
        }

        void client_SendDataCompleted(object sender, AsyncCompletedEventArgs e)
        {
            RefreshTimer();
            if (ErrorOccured(e))
            {
                return;
            }
            this.SendFileJunk();
        }

        private void SendFileJunk()
        {
            var read = stream.Read(buffer, 0, buffer.Length);
            if (read > 0)
            {
                client.SendDataAsync(handle, buffer, 0, read);
            }
            else
            {
                client.EndCompleted += new EventHandler<AsyncCompletedEventArgs>(client_EndCompleted);
                client.EndAsync(handle);
                handle = new Guid();
            }
        }

        void client_EndCompleted(object sender, AsyncCompletedEventArgs e)
        {
            if (ErrorOccured(e))
            {
                return;
            }
            this.InvokeFinished(new TaskFinishedEventArgs(null));
            if (client != null)
            {
                this.client.CloseAsync();
            }
        }

        public bool IsStarted
        {
            get
            {
                return isStarted;
            }
        }

        public bool IsFinished
        {
            get
            {
                return isFinished;
            }
        }

        public event EventHandler<TaskFinishedEventArgs> Finished;

        protected void InvokeFinished(TaskFinishedEventArgs e)
        {
            if (timer != null)
            {
                timer.Dispose();
                timer = null;
            } 
            if (stream != null)
            {
                stream.Dispose();
            }
            isFinished = true;
            EventHandler<TaskFinishedEventArgs> handler = this.Finished;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        public event EventHandler<TaskUpdateEventArgs> Update;

        public event EventHandler<TaskErrorEventArgs> Error;

        public void InvokeError(TaskErrorEventArgs e)
        {
            if (stream != null)
            {
                stream.Dispose();
            }
            isFinished = true;
            EventHandler<TaskErrorEventArgs> handler = this.Error;
            if (handler != null)
            {
                handler(this, e);
            }
        }
    }
    public class DownloadTask : ITask
    {
        private Stream stream;
        private readonly Func<Stream> getStream;

        private readonly string path;

        private FileExchangeServiceClient client;

        private Guid handle;

        private bool isStarted;

        private bool isFinished;

        private Timer timer;

        public DownloadTask(Func<Stream> stream, string path)
        {
            this.getStream = stream;
            this.path = path;
        }

        public void Start()
        {
            if (isStarted)
            {
                return;
            }
            isStarted = true;
            stream = getStream();


            timer = new Timer(TimeoutReached, null, ServiceConnection.TimeOut, ServiceConnection.TimeOut);

            client = ServiceConnection.GetClient();
            client.StartCompleted += new EventHandler<StartCompletedEventArgs>(client_StartCompleted);
            client.StartAsync(this.path, true);
        }

        void TimeoutReached(object state)
        {
            if (timer != null)
            {
                timer.Dispose();
                timer = null;
            }

            if (!isFinished)
            {
                var clientLocal = client;
                if (clientLocal != null)
                {
                    var thread = new Thread(
                        () => clientLocal.Abort());
                    thread.Start();
                    if (!thread.Join(1000))
                    {
                        // thread.Abort();
                    }
                    client = null;
                }

                this.InvokeError(new TaskErrorEventArgs(new TimeoutException("Timeout reached!")));
            }
        }

        void RefreshTimer()
        {
            if (timer != null)
            {
                timer.Change(ServiceConnection.TimeOut, ServiceConnection.TimeOut);
            }
        }

        void client_StartCompleted(object sender, StartCompletedEventArgs e)
        {
            if (ErrorOccured(e))
            {
                return;
            }
            handle = e.Result;
            client.ReadDataCompleted += new EventHandler<ReadDataCompletedEventArgs>(client_ReadDataCompleted);
            client.ReadDataAsync(handle, ServiceConnection.ReceiveBufferSize);
        }

        void client_ReadDataCompleted(object sender, ReadDataCompletedEventArgs e)
        {
            RefreshTimer();
            if (ErrorOccured(e))
            {
                return;
            }
            this.ReadFileJunk(e.Result);
        }

        private bool ErrorOccured(AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                if (timer != null)
                {
                    timer.Dispose();
                    timer = null;
                }
                this.InvokeError(new TaskErrorEventArgs(e.Error));
                return true;
            }

            return false;
        }

        private void ReadFileJunk(byte[] received)
        {
            if (received.Length > 0)
            {
                stream.Write(received, 0, received.Length);
                this.client.ReadDataAsync(handle, ServiceConnection.ReceiveBufferSize);
            }
            else
            {
                // We are finished
                if (stream != null)
                {
                    stream.Dispose();
                    stream = null;
                }
                client.ReadDataCompleted -= this.client_ReadDataCompleted;
                client.EndCompleted += this.client_EndCompleted;
                client.EndAsync(handle);
                handle = new Guid();

            }
        }

        void client_EndCompleted(object sender, AsyncCompletedEventArgs e)
        {
            if (ErrorOccured(e))
            {
                return;
            }
            client.EndCompleted -= this.client_EndCompleted;
            this.InvokeFinished(new TaskFinishedEventArgs(null));
        }

        public bool IsStarted
        {
            get
            {
                return isStarted;
            }
        }

        public bool IsFinished
        {
            get
            {
                return isFinished;
            }
        }

        public event EventHandler<TaskFinishedEventArgs> Finished;

        protected void InvokeFinished(TaskFinishedEventArgs e)
        {
            if (timer != null)
            {
                timer.Dispose();
                timer = null;
            }
            if (stream != null)
            {
                stream.Dispose();
            }
            isFinished = true;
            EventHandler<TaskFinishedEventArgs> handler = this.Finished;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        public event EventHandler<TaskUpdateEventArgs> Update;

        public event EventHandler<TaskErrorEventArgs> Error;

        public void InvokeError(TaskErrorEventArgs e)
        {
            if (stream != null)
            {
                stream.Dispose();
            }
            isFinished = true;
            EventHandler<TaskErrorEventArgs> handler = this.Error;
            if (handler != null)
            {
                handler(this, e);
            }
        }
    }

    public class TaskUpdateEventArgs : EventArgs
    {
        public TaskUpdateEventArgs(object data)
        {
            this.Data = data;
        }

        public object Data { get; private set; }
    }

    public class TaskFinishedEventArgs : EventArgs
    {
        public TaskFinishedEventArgs(object data)
        {
            this.Data = data;
        }

        public object Data { get; private set; }
    }

    public static class ServiceConnection
    {
        public const long TimeOut = 1000 * 60 * 1;

        internal const int ReceiveBufferSize = 1024 * 12;
        internal const int SendBufferSize = 1024 * 12;

        public static ITask UploadTask(Func<Stream> stream, string path)
        {
            return new UploadTask(stream, path);
        }

        public static ITask DownloadTask(Func<Stream> writeTo, string path)
        {
            return new DownloadTask(writeTo, path);
        }

        public static FileExchangeServiceClient GetClient()
        {
            var address = new EndpointAddress(Settings.Current.RemoteServer.ToString());
            var client = new FileExchangeServiceClient("BasicHttpBinding_IFileExchangeService", address);
            return client;
        }

        public static IEnumerable<FileElement> EnumerateEntries(string currentPath)
        {
            var address = new EndpointAddress(Settings.Current.RemoteServer.ToString());
            return new FileElementEnumerable(currentPath);
        }
    }

    public class FileElementEnumerable : IEnumerable<FileElement>
    {
        private readonly string currentPath;

        public FileElementEnumerable(string currentPath)
        {
            this.currentPath = currentPath;
        }

        public IEnumerator<FileElement> GetEnumerator()
        {
            var address = new EndpointAddress(Settings.Current.RemoteServer.ToString());
            
            var client = new FileExchangeServiceClient("BasicHttpBinding_IFileExchangeService", address);
            return new FileElementEnumerator(currentPath, client);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    }

    public class FileElementEnumerator : IEnumerator<FileElement>
    {
        private readonly string path;

        private FileExchangeServiceClient client;

        public FileElementEnumerator(string address, FileExchangeServiceClient client)
        {
            this.path = address;
            this.client = client;

            client.StartEnumerationCompleted += this.client_StartEnumerationCompleted;
            client.StartEnumerationAsync(path);
        }

        public void Dispose()
        {
            this.client = null;
        }

        private bool isStarted;

        Queue<FileElement> elements = new Queue<FileElement>();

        private bool isFinished;

        public bool MoveNext()
        {
            while (!isStarted)
            {
                Thread.Sleep(200);
            }

            do
            {
                lock (workingSync)
                {
                    if (error != null)
                    {
                        throw error;
                    }

                    int elementCount = elements.Count;
                    bool hasNoElems = elementCount <= 0;

                    if (!hasNoElems)
                    {
                        current = elements.Dequeue();
                        return true;
                    }

                    if (this.isFinished)
                    {
                        current = null;
                        return false;
                    }
                }

                Thread.Sleep(200);
            }
            while (true);
        }
        
        void client_StartEnumerationCompleted(object sender, StartEnumerationCompletedEventArgs e)
        {
            lock (workingSync)
            {
                error = e.Error;
                if (error == null)
                {
                    handle = e.Result;
                }

                isStarted = true;
            }

            client.StartEnumerationCompleted -= this.client_StartEnumerationCompleted;
            client.GetNextCompleted += this.client_GetNextCompleted;
            client.GetNextAsync(handle);
        }

        void client_GetNextCompleted(object sender, GetNextCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                error = e.Error;
                return;
            }

            foreach (var fileElement in e.Result.Content)
            {
                lock (workingSync)
                {
                    elements.Enqueue(fileElement);
                }
            } 

            if (e.Result.IsMore)
            {
                client.GetNextAsync(handle);
            }
            else
            {
                handle = new Guid();
                client.GetNextCompleted -= this.client_GetNextCompleted;
                client.CloseCompleted += new EventHandler<AsyncCompletedEventArgs>(client_CloseCompleted);
                client.CloseAsync();
            }
        }

        void client_CloseCompleted(object sender, AsyncCompletedEventArgs e)
        {
            lock (workingSync)
            {
                if (e.Error != null)
                {
                    error = e.Error;
                    return;
                }
                isFinished = true;
            }
        }

        public void Reset()
        {
            throw new NotSupportedException();
        }

        private FileElement current;

        private Exception error;

        private Guid handle;

        private object workingSync = new object();

        public FileElement Current
        {
            get
            {
                if (current == null)
                {
                    throw new InvalidOperationException();
                }

                return current;
            }
        }

        object IEnumerator.Current
        {
            get
            {
                return Current;
            }
        }
    }
}